home *** CD-ROM | disk | FTP | other *** search
Wrap
/* File: PlaySound.c Description: Play Sound shows how to use the Sound Description Extention atom information with the SoundConverter APIs to play non VBR MP3 files as well as other types of sound files using various encoding methods. Author: mc, era Copyright: © Copyright 2000 Apple Computer, Inc. All rights reserved. Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple’s copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Change History (most recent first): <2> 7/26/00 sans hickup and carbonized <1> 4/01/00 initial release */ #include "MP3Player.h" // globals Boolean gBufferDone = false; // * ---------------------------- // MySoundCallBackFunction // // used to signal when a buffer is done playing static pascal void MySoundCallBackFunction(SndChannelPtr theChannel, SndCommand *theCmd); static pascal void MySoundCallBackFunction(SndChannelPtr theChannel, SndCommand *theCmd) { #pragma unused(theChannel) #ifndef TARGET_API_MAC_CARBON #if !GENERATINGCFM long oldA5; oldA5 = SetA5(theCmd->param2); #else #pragma unused(theCmd) #endif #else #pragma unused(theCmd) #endif // TARGET_API_MAC_CARBON gBufferDone = true; #ifndef TARGET_API_MAC_CARBON #if !GENERATINGCFM oldA5 = SetA5(oldA5); #endif #endif // TARGET_API_MAC_CARBON } // * ---------------------------- // MyGetSoundDescriptionExtension // // this function will extract the information needed to decompress the sound file, this includes retrieving the sample description, // the decompression atom, setting up the sound header, copying the sample data into a sample buffer and calculating it's length static OSErr MyGetSoundDescriptionExtension(const FSSpec *inMP3file, AudioFormatAtomPtr *outAudioAtom, CmpSoundHeaderPtr outSoundHeader, Handle outSampleBuf, UInt32 *outLength); static OSErr MyGetSoundDescriptionExtension(const FSSpec *inMP3file, AudioFormatAtomPtr *outAudioAtom, CmpSoundHeaderPtr outSoundHeader, Handle outSampleBuf, UInt32 *outLength) { Movie theMovie; Track theTrack; Media theMedia; short theRefNum; short theResID = 0; // we want the first movie Boolean wasChanged; OSErr err = noErr; // open the movie file err = OpenMovieFile(inMP3file, &theRefNum, fsRdPerm); BailErr(err); // instantiate the movie err = NewMovieFromFile(&theMovie, theRefNum, &theResID, NULL, newMovieActive, &wasChanged); BailErr(err); CloseMovieFile(theRefNum); theRefNum = 0; // get the first sound track theTrack = GetMovieIndTrackType(theMovie, 1, SoundMediaType, movieTrackMediaType); if (theTrack != NULL) { // get the sound track media theMedia = GetTrackMedia(theTrack); if (theMedia != NULL) { Size size; Handle extension; // Version 1 of this record includes four extra fields to store information about compression ratios. It also defines // how other extensions are added to the SoundDescription record. // All other additions to the SoundDescription record are made using QT atoms. That means one or more // atoms can be appended to the end of the SoundDescription record using the standard [size, type] // mechanism used throughout the QuickTime movie resource architecture. // http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm SoundDescriptionV1Handle sourceSoundDescription = (SoundDescriptionV1Handle)NewHandle(0); // get the description of the sample data GetMediaSampleDescription(theMedia, 1, (SampleDescriptionHandle)sourceSoundDescription); err = GetMoviesError(); extension = NewHandle(0); // get the "magic" decompression atom // This extension to the SoundDescription information stores data specific to a given audio decompressor. // Some audio decompression algorithms require a set of out-of-stream values to configure the decompressor // which are stored in a siDecompressionParams atom. The contents of the siDecompressionParams atom are dependent // on the audio decompressor. err = GetSoundDescriptionExtension((SoundDescriptionHandle)sourceSoundDescription, &extension, siDecompressionParams); if (noErr == err) { size = GetHandleSize(extension); HLock(extension); *outAudioAtom = (AudioFormatAtom*)NewPtr(size); err = MemError(); // copy the atom data to our buffer... BlockMoveData(*extension, *outAudioAtom, size); HUnlock(extension); } else { // if it doesn't have an atom, that's ok err = noErr; } // set up our sound header outSoundHeader->format = (*sourceSoundDescription)->desc.dataFormat; outSoundHeader->numChannels = (*sourceSoundDescription)->desc.numChannels; outSoundHeader->sampleSize = (*sourceSoundDescription)->desc.sampleSize; outSoundHeader->sampleRate = (*sourceSoundDescription)->desc.sampleRate; if (noErr == err) { // copy the sample data into our buffer TimeValue trackDuration = GetTrackDuration(theTrack); TimeValue startTime = 0; long numMediaSamples = GetMediaSampleCount(theMedia); long numberOfSamples; Handle hTemp = NewHandle(0); do { err = GetMediaSample(theMedia, hTemp, 0L, NULL, startTime, NULL, 0, NULL, NULL, numMediaSamples, &numberOfSamples, NULL); startTime += numberOfSamples; HandAndHand(hTemp, outSampleBuf); } while (startTime < numMediaSamples && noErr == err ); DisposeHandle(hTemp); // calculate the duration of the data // although we should be able to use the samplesPerPacket value returned in the SoundDescriptionV1 record to see if we're dealing with multiple samples per packet // as per the documentation, and I quote "If these fields are not used, they are set to 0. File readers only need to check to see if samplesPerPacket is 0." // in the case when a sound track is exported as a .mov using for example µLaw 2:1 compression these fields are filed with 'spore' aka 'grunge' so... // use GetCompressionInfo and check samplesPerPacket NOTE: Although better than nothing this will not always work, for example a .mov file with a SoundTrack // encoded using MACE where samples per packet will be greater than 1 but the information in the SoundDescriptionV1 record will be wrong CompressionInfo compInfo; err = GetCompressionInfo(fixedCompression, (*sourceSoundDescription)->desc.dataFormat, (*sourceSoundDescription)->desc.numChannels, (*sourceSoundDescription)->desc.sampleSize, &compInfo); // numFrames = samples / samplesPerPacket // duration = numFrames * bytesPerFrame if (compInfo.samplesPerPacket == 1) { *outLength = (((numMediaSamples / compInfo.samplesPerPacket) * compInfo.bytesPerFrame)); } else { *outLength = (((numMediaSamples / (*sourceSoundDescription)->samplesPerPacket) * (*sourceSoundDescription)->bytesPerFrame)); } } DisposeHandle(extension); DisposeHandle((Handle)sourceSoundDescription); } } bail: return err; } // * ---------------------------- // PlaySound // // this function does the actual work of playing the sound file, it sets up the sound converter environment, allocates play buffers, // creates the sound channel and sends the appropriate sound commands to play the converted sound data OSErr PlaySound(const FSSpec *inFileToPlay) { Handle hSoundData = NULL; UInt32 theLength = 0; AudioCompressionAtomPtr theDecompressionAtom; SoundComponentData theInputFormat, theOutputFormat; SoundConverter mySoundConverter = NULL; CmpSoundHeader mySndHeader0, mySndHeader1; Ptr pSourceBuffer = NULL; Ptr pDecomBuffer0 = NULL, pDecomBuffer1 = NULL; Boolean isSoundDone = false; FInfo fndrInfo; OSErr err = noErr; err = FSpGetFInfo(inFileToPlay, &fndrInfo); BailErr(err); hSoundData = NewHandle(0); if (hSoundData == NULL || MemError()) goto bail; // get what we need to do what we need to do err = MyGetSoundDescriptionExtension(inFileToPlay, (AudioFormatAtomPtr *)&theDecompressionAtom, &mySndHeader0, hSoundData, &theLength); if (noErr == err) { HLock(hSoundData); pSourceBuffer = *hSoundData; // source buffer // setup input/output format for sound converter theInputFormat.flags = 0; theInputFormat.format = mySndHeader0.format; theInputFormat.numChannels = mySndHeader0.numChannels; theInputFormat.sampleSize = mySndHeader0.sampleSize; theInputFormat.sampleRate = mySndHeader0. sampleRate; theInputFormat.sampleCount = 0; theInputFormat.buffer = NULL; theInputFormat.reserved = 0; theOutputFormat.flags = 0; theOutputFormat.format = kSoundNotCompressed; theOutputFormat.numChannels = theInputFormat.numChannels; theOutputFormat.sampleSize = theInputFormat.sampleSize; theOutputFormat.sampleRate = theInputFormat.sampleRate; theOutputFormat.sampleCount = 0; theOutputFormat.buffer = NULL; theOutputFormat.reserved = 0; err = SoundConverterOpen(&theInputFormat, &theOutputFormat, &mySoundConverter); BailErr(err); // set up the sound converters decompresson 'environment' by passing in the 'magic' decompression atom err = SoundConverterSetInfo(mySoundConverter, siDecompressionParams, theDecompressionAtom); if (siUnknownInfoType == err) { // clear this error, the decompressor didn't // need the decompression atom and that's OK err = noErr; } else BailErr(err); UInt32 targetBytes = 32768, inputFrames = 0, inputBytes = 0, outputFrames = 0, outputBytes = 0, actualOutputBytes = 0, bytesConverted = 0, bytesPerFrame = 0; // find out how much buffer space to alocate for our output buffers do { targetBytes *= 2; err = SoundConverterGetBufferSizes(mySoundConverter, targetBytes, &inputFrames, &inputBytes, &outputBytes); } while (notEnoughBufferSpace == err && targetBytes < (MaxBlock() / 4)); bytesPerFrame = inputBytes / inputFrames; pDecomBuffer0 = NewPtr(outputBytes + kOverflowRoom); BailErr(MemError();); pDecomBuffer1 = NewPtr(outputBytes + kOverflowRoom); BailErr(MemError();); // convert two buffers of sound before we begin to do anything else err = SoundConverterBeginConversion(mySoundConverter); BailErr(err); // setup first header mySndHeader0.samplePtr = pDecomBuffer0; mySndHeader0.numChannels = theOutputFormat.numChannels; mySndHeader0.sampleRate = theOutputFormat.sampleRate; mySndHeader0.loopStart = 0; mySndHeader0.loopEnd = 0; mySndHeader0.encode = cmpSH; // compressed sound header encode value mySndHeader0.baseFrequency = kMiddleC; // mySndHeader0.AIFFSampleRate; // this is not used mySndHeader0.markerChunk = NULL; mySndHeader0.format = theOutputFormat.format; mySndHeader0.futureUse2 = 0; mySndHeader0.stateVars = NULL; mySndHeader0.leftOverSamples = NULL; mySndHeader0.compressionID = fixedCompression; // compression ID for fixed-sized compression, even uncompressed sounds use fixedCompression mySndHeader0.packetSize = 0; // the Sound Manager will figure this out for us mySndHeader0.snthID = 0; mySndHeader0.sampleSize = theOutputFormat.sampleSize; mySndHeader0.sampleArea[0] = 0; // no samples here because we use samplePtr to point to our buffer instead // setup second header, only the buffer ptr is different BlockMoveData(&mySndHeader0, &mySndHeader1, sizeof(mySndHeader0)); mySndHeader1.samplePtr = pDecomBuffer1; bytesConverted = 0; err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer, inputFrames, pDecomBuffer0, &outputFrames, &actualOutputBytes); BailErr(err); if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer"); bytesConverted += inputBytes; mySndHeader0.numFrames = outputFrames; if (bytesConverted > theLength) { isSoundDone = true; inputBytes = 0; inputFrames = 0; } else if (bytesConverted + inputBytes > theLength) { isSoundDone = true; inputBytes = theLength - bytesConverted; inputFrames = inputBytes / bytesPerFrame; } err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer + bytesConverted, inputFrames, pDecomBuffer1, &outputFrames, &actualOutputBytes); BailErr(err); if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer"); bytesConverted += inputBytes; mySndHeader1.numFrames = outputFrames; // setup the callback, create the sound channel and play the sound // we will continue to convert the sound data into the free (non playing) buffer SndCallBackUPP theSoundCallBackUPP = NewSndCallBackUPP(MySoundCallBackFunction); SndChannelPtr pSoundChannel = NULL; err = SndNewChannel(&pSoundChannel, sampledSynth, 0, theSoundCallBackUPP); if (err == noErr) { SndCommand thePlayCmd0, thePlayCmd1, theCallBackCmd; SndCommand *pPlayCmd; eBufferNumber whichBuffer = kFirstBuffer; thePlayCmd0.cmd = bufferCmd; thePlayCmd0.param1 = 0; // not used, but clear it out anyway just to be safe thePlayCmd0.param2 = (long)&mySndHeader0; thePlayCmd1.cmd = bufferCmd; thePlayCmd1.param1 = 0; // not used, but clear it out anyway just to be safe thePlayCmd1.param2 = (long)&mySndHeader1; whichBuffer = kFirstBuffer; // buffer 1 will be free when callback runs theCallBackCmd.cmd = callBackCmd; theCallBackCmd.param2 = SetCurrentA5(); gBufferDone = false; err = SndDoCommand(pSoundChannel, &thePlayCmd0, true); if (noErr == err) { err = SndDoCommand(pSoundChannel, &theCallBackCmd, true); } if (noErr == err) { err = SndDoCommand(pSoundChannel, &thePlayCmd1, true); } Ptr pDecomBuffer = NULL; CmpSoundHeaderPtr pSndHeader = NULL; if (noErr == err) { while (!isSoundDone && !Button()) { if (gBufferDone == true) { if (kFirstBuffer == whichBuffer) { pPlayCmd = &thePlayCmd0; pDecomBuffer = pDecomBuffer0; pSndHeader = &mySndHeader0; whichBuffer = kSecondBuffer; } else { pPlayCmd = &thePlayCmd1; pDecomBuffer = pDecomBuffer1; pSndHeader = &mySndHeader1; whichBuffer = kFirstBuffer; } if (bytesConverted < theLength) { if (bytesConverted + inputBytes >= theLength) { isSoundDone = true; inputBytes = theLength - bytesConverted; inputFrames = inputBytes / bytesPerFrame; } err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer + bytesConverted, inputFrames, pDecomBuffer, &outputFrames, &actualOutputBytes); if (err) break; if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer"); bytesConverted += inputBytes; pSndHeader->numFrames = outputFrames; gBufferDone = false; if (!isSoundDone) { SndDoCommand(pSoundChannel, &theCallBackCmd, true); // reuse callBackCmd } SndDoCommand(pSoundChannel, pPlayCmd, true); // play the next buffer } } } // while } SoundConverterEndConversion(mySoundConverter, pDecomBuffer, &outputFrames, &outputBytes); if (noErr == err && outputFrames) { pSndHeader->numFrames = outputFrames; SndDoCommand(pSoundChannel, pPlayCmd, true); // play the last buffer. } } if (theSoundCallBackUPP) DisposeSndCallBackUPP(theSoundCallBackUPP); if (pSoundChannel) { err = SndDisposeChannel(pSoundChannel, false); // wait until sounds stops playing before disposing of channel } } bail: if (mySoundConverter) SoundConverterClose(mySoundConverter); if (pDecomBuffer0) DisposePtr(pDecomBuffer0); if (pDecomBuffer1) DisposePtr(pDecomBuffer1); if (hSoundData) DisposeHandle(hSoundData); return err; }